package com.powernode.map.service.impl;


import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.powernode.common.constant.RedisConstant;
import com.powernode.common.constant.SystemConstant;
import com.powernode.common.util.LocationUtil;
import com.powernode.driver.client.DriverInfoFeignClient;
import com.powernode.map.repository.OrderServiceLocationRepository;
import com.powernode.map.service.LocationService;
import com.powernode.model.entity.driver.DriverSet;
import com.powernode.model.entity.map.OrderServiceLocation;
import com.powernode.model.form.map.OrderServiceLocationForm;
import com.powernode.model.form.map.SearchNearByDriverForm;
import com.powernode.model.form.map.UpdateDriverLocationForm;
import com.powernode.model.form.map.UpdateOrderLocationForm;
import com.powernode.model.vo.map.NearByDriverVo;
import com.powernode.model.vo.map.OrderLocationVo;
import com.powernode.model.vo.map.OrderServiceLastLocationVo;
import com.powernode.order.client.OrderInfoFeignClient;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private DriverInfoFeignClient driverInfoFeignClient;

    @Resource
    private OrderServiceLocationRepository orderServiceLocationRepository;

    @Resource
    private MongoTemplate mongoTemplate;
    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    /**
     * 将配送员的位置信息存储到redis数据库中
     */
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {

        //将配送员的信息存入redis  Point是spring的包下
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(), updateDriverLocationForm.getLatitude().doubleValue());

        //这里使用的是opsForGeo
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION, point, updateDriverLocationForm.getDriverId() + "");

        return true;
    }


    /**
     * 删除配送员的位置信息  下班了
     */
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId);
        return true;
    }


    /**
     * 搜索附近合适的配送员
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {

        //查询附近5公里范围内的配送员
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());

        //定义距离 5公里
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);

        //以point为中心点 距离为5公里 画圆
        Circle circle = new Circle(point, distance);

        //构建geo参数
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands
                        .GeoRadiusCommandArgs.newGeoRadiusArgs().
                        includeCoordinates()  //包含坐标
                        .includeDistance()   //距离
                        .sortAscending();   // 排序 升序

        //redis搜索信息、
        GeoResults<RedisGeoCommands.GeoLocation<String>> result = redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

        //获取信息
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

        //保存符合派单条件的配送员
        List<NearByDriverVo> list = new ArrayList<>();

        if (CollectionUtils.isNotEmpty(content)) {
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();

            while (iterator.hasNext()) {
                GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();

                //获取配送员的id  更新配送员位置的时候，存储了经纬度和配送员的id，这里的getName获取的就是配送员的id
                String name = item.getContent().getName();
                long driverId = Long.parseLong(name);

                //距离
                BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, BigDecimal.ROUND_HALF_UP);


                //获取配送员个性化参数
                DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
                //判断当前订单是否符合配送员的个性化设置
                if (driverSet.getAcceptDistance().doubleValue() != 0 && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0) {
                    continue;
                }

                //搜索满足条件的配送员
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);

            }
        }

        return list;
    }


    /**
     * 位置同显 更新配送员位置信息
     *
     * @param updateOrderLocationForm
     * @return
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());

        //将位置数据放入redis
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(), orderLocationVo);

        return true;
    }


    /*
        获取订单起始位置信息
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        return (OrderLocationVo) redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
    }

    /**
     * 配送员开始配送之后，将自己的位置信息上传
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderServiceLocationForms) {
        //将前端传入的类型转成实体entity OrderServiceLocation
        List<OrderServiceLocation> list = new ArrayList<>();

        orderServiceLocationForms.forEach(form -> {
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();

            BeanUtils.copyProperties(form, orderServiceLocation);
            //生成id
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());

            list.add(orderServiceLocation);
        });

        //将位置信息放入mongodb中
        orderServiceLocationRepository.saveAll(list);

        return true;
    }


    /**
     * 获取配送员最后的位置信息
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));//根据订单编号查询
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));//根据创建时间排序，找最近的时间
        query.limit(1);//若时间相同，则查询1条数据

        //操作mongodb
        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);

        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);

        return orderServiceLastLocationVo;
    }

    /**
     * 计算实际距离
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //从mongodb中查询标点串
        List<OrderServiceLocation> orderServiceLocationList = orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);

        //最终实际距离
        double realDistance = 0;

        if (CollectionUtils.isNotEmpty(orderServiceLocationList)) {
            //遍历标点穿list获取坐标
            for (int i = 0, size = orderServiceLocationList.size() - 1; i < size; i++) {
                //上一个位置信息
                OrderServiceLocation start = orderServiceLocationList.get(i);
                //下一个位置信息
                OrderServiceLocation end = orderServiceLocationList.get(i + 1);

                //利用工具类计算上面两个位置的距离
                double distance = LocationUtil.getDistance(start.getLatitude().doubleValue(), start.getLongitude().doubleValue(), end.getLatitude().doubleValue(), end.getLongitude().doubleValue());

                realDistance += distance;
            }
        }

        //我们在模拟器中无法移动，所以这里模拟了一个距离
        if (realDistance == 0) {
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal(1));
        }

        return new BigDecimal(realDistance);
    }
}
